#include <basic/errcode.h>
#include <basic/sock.h>
#include <basic/para.h>
#include <custom/errcode.h>
#include <custom/svcnode.h>

#define _CONN_TIMEOUT         5
#define _CALL_TIMEOUT         10

struct _svcnode
{/*{{{*/
    unsigned int mode;
    struct host_addr_info *ready;
    int ready_num;
    SOCKET *ready_sock;
    struct host_addr_info *backup;
    SOCKET backup_sock;
};/*}}}*/

int sn_create(char *addr, unsigned int mode, SVCNODE *psn)
{/*{{{*/
    int res;
    int idx;
    SVCNODE sn;

    if(!psn)
       return(RET_ERR_PARA);
    *psn=(SVCNODE)NULL;
    if(!addr ||
            (mode != SN_MODE_PERMANENT && mode != SN_MODE_ONESHOT))
       return(RET_ERR_PARA);

    ALLOC_RETERR(sn, struct _svcnode, RET_ERR_ALLOC);
    sn->mode=mode;
    if((res=host_list_parse(addr, &sn->ready, &sn->ready_num,
                    &sn->backup)))
    {   free(sn); return(-1);   }
    ALLOC_MULTI_DOERR(sn->ready_sock, SOCKET, sn->ready_num,
            free(sn->ready); FREE(sn->backup); free(sn);
            return(RET_ERR_ALLOC));
    for(idx=0; idx<sn->ready_num; idx++)
    {
        if(sn->ready[idx].port < 10000)
        {
            free(sn->ready); FREE(sn->backup); free(sn);
            return(-2);
        }
        sn->ready_sock[idx]=INVALID_SOCKET;
    }
    if(sn->backup && sn->backup->port < 10000)
    {
        free(sn->ready); free(sn->backup); free(sn);
        return(-2);
    }
    sn->backup_sock=INVALID_SOCKET;
    *psn=sn;

    return(0);
}/*}}}*/

int sn_has_ready(SVCNODE sn)
{/*{{{*/
    if(sn)
        return(sn->ready_num);
    else
        return(RET_ERR_PARA);
}/*}}}*/

int sn_has_backup(SVCNODE sn)
{/*{{{*/
    if(sn)
    {
        if(sn->backup)
            return(1);
        else
            return(0);
    }
    else
        return(RET_ERR_PARA);
}/*}}}*/

void sn_free(SVCNODE sn)
{/*{{{*/
    if(sn)
    {
        int i;

        free(sn->ready);
        for(i=0; i<sn->ready_num; i++)
            sock_close(sn->ready_sock[i]);
        free(sn->ready_sock);
        FREE(sn->backup);
        sock_close(sn->backup_sock);
        free(sn);
    }
}/*}}}*/

int sn_call(SVCNODE sn, char *svc, char *request, char *respond)
{/*{{{*/
    int res;
    int request_size;
    int respond_size;
    int idx;
    int *idxes;
    int ready_idx=-1;
    SOCKET *psock;

    if(!sn)
        return(RET_ERR_PARA);
    if(!sn || 
            (svc && ((res=strlen(svc)) <= 0 || res > NAME_SIZE)) ||
            (request && (res=strlen(request)) >
             SOCK_MSG_SIZE-NAME_SIZE-16) ||
            !respond)
    {
        para_put_item(respond, SOAR_ERR_PARA);
        return(RET_ERR_PARA);
    }

    ALLOC_MULTI_RETERR(idxes, int, sn->ready_num+1, RET_ERR_ALLOC);
    for(idx=0; idx<sn->ready_num; idx++)
        idxes[idx]=idx;
    random_array_int(idxes, sn->ready_num);
    for(idx=0; idx<sn->ready_num; idx++)
    {
        ready_idx=idxes[idx];
        // 已有连接
        if(sn->ready_sock[ready_idx] != INVALID_SOCKET)
        {
            psock=&sn->ready_sock[ready_idx];
            CLR_BUF(respond);
            if(svc)
                para_put_item(respond, svc);
            if(request)
                strcat(respond, request);
            request_size=strlen(respond);
            if((res=sock_write(*psock, (unsigned char *)respond,
                            &request_size, _CALL_TIMEOUT, WRAP)) == 0)
            {
                if((res=sock_read(*psock, (unsigned char *)respond,
                                &respond_size, _CALL_TIMEOUT, WRAP))
                        == 0)
                {
                    respond[respond_size]=ZERO;
                    free(idxes);
                    goto OUT_OK;
                }
            }
            sock_close(*psock);
            *psock=INVALID_SOCKET;
        }
        // 建立连接
        if((sn->ready_sock[ready_idx]=sock_connect(
                        sn->ready[ready_idx].host,
                        sn->ready[ready_idx].port,
                        _CONN_TIMEOUT)) != INVALID_SOCKET)
            break;
    }
    free(idxes);

    if(idx == sn->ready_num)    // 无ready可用，使用backup
    {
        if(!sn->backup ||
                (sn->backup_sock == INVALID_SOCKET &&
                 (sn->backup_sock=sock_connect(
                            sn->backup->host,
                            sn->backup->port,
                            _CONN_TIMEOUT)) == INVALID_SOCKET))
        {
            CLR_BUF(respond);
            para_put_item(respond, SOAR_ERR_COMM_FAIL);
            return(-1);
        }
        psock=&sn->backup_sock;
    }
    else    // 某ready
        psock=&sn->ready_sock[ready_idx];

    // 新连接通信
    CLR_BUF(respond);
    if(svc)
        para_put_item(respond, svc);
    if(request)
        strcat(respond, request);
    request_size=strlen(respond);
    if((res=sock_write(*psock, (unsigned char *)respond,
                    &request_size, _CALL_TIMEOUT, WRAP)) != 0 ||
            (res=sock_read(*psock, (unsigned char *)respond,
                           &respond_size, _CALL_TIMEOUT, WRAP)) != 0)
    {
        sock_close(*psock);
        *psock=INVALID_SOCKET;
        CLR_BUF(respond);
        if(res == RET_ERR_TIMEOUT)
        {
            para_put_item(respond, SOAR_ERR_CALL_TIMEOUT);
            return(res);
        }
        else
        {
            para_put_item(respond, SOAR_ERR_COMM_FAIL);
            return(-2);
        }
    }

OUT_OK:
    if(sn->mode == SN_MODE_ONESHOT)
    {
        sock_close(*psock);
        *psock=INVALID_SOCKET;
    }
    return(0);
}/*}}}*/

int sn_call_manual(SVCNODE sn, int manual_idx, char *svc,
        char *request, char *respond)
{/*{{{*/
    int res;
    int request_size;
    int respond_size;
    SOCKET *psock;
    char *host;
    unsigned int port;

    if(!sn ||
            (svc && ((res=strlen(svc)) <= 0 ||
                res > NAME_SIZE)) ||
        (request && (res=strlen(request)) >
         SOCK_MSG_SIZE-NAME_SIZE-16) ||
        !respond)
    {
        para_put_item(respond, SOAR_ERR_PARA);
        return(RET_ERR_PARA);
    }

    if(manual_idx > sn->ready_num-1)
        return(-1); 
    if(manual_idx >= 0)
    {
        psock=&sn->ready_sock[manual_idx];
        host=sn->ready[manual_idx].host;
        port=sn->ready[manual_idx].port;
    }
    else
    {
        if(!sn->backup)
            return(-2);
        psock=&sn->backup_sock;
        host=sn->backup->host;
        port=sn->backup->port;
    }
    if(*psock != INVALID_SOCKET)
    {
        CLR_BUF(respond);
        if(svc)
            para_put_item(respond, svc);
        if(request)
            strcat(respond, request);
        request_size=strlen(respond);
        if((res=sock_write(*psock, (unsigned char *)respond,
                        &request_size, _CALL_TIMEOUT, WRAP)) == 0)
        {
            if((res=sock_read(*psock, (unsigned char *)respond,
                            &respond_size, _CALL_TIMEOUT, WRAP)) == 0)
            {
                respond[respond_size]=ZERO;
                goto OUT_OK;
            }
        }
        sock_close(*psock);
        *psock=INVALID_SOCKET;
    }

    if(((*psock)=sock_connect(host, port, _CONN_TIMEOUT)) ==
            INVALID_SOCKET)
    {
        CLR_BUF(respond);
        para_put_item(respond, SOAR_ERR_COMM_FAIL);
        return(-3);
    }
    CLR_BUF(respond);
    if(svc)
        para_put_item(respond, svc);
    if(request)
        strcat(respond, request);
    request_size=strlen(respond);
    if((res=sock_write(*psock, (unsigned char *)respond,
                    &request_size, _CALL_TIMEOUT, WRAP)) != 0 ||
            (res=sock_read(*psock, (unsigned char *)respond,
                           &respond_size, _CALL_TIMEOUT, WRAP)) != 0)
    {
        sock_close(*psock);
        *psock=INVALID_SOCKET;
        CLR_BUF(respond);
        if(res == RET_ERR_TIMEOUT)
        {
            para_put_item(respond, SOAR_ERR_CALL_TIMEOUT);
            return(res);
        }
        else
        {
            para_put_item(respond, SOAR_ERR_COMM_FAIL);
            return(-4);
        }
    }

OUT_OK:
    if(sn->mode == SN_MODE_ONESHOT)
    {
        sock_close(*psock);
        *psock=INVALID_SOCKET;
    }
    return(0);
}/*}}}*/

static int _send_mail(char *svc,
        char *mailfwd_addr,
        char *mailto, char *mailfrom, char *replyto,
        char *subject, char *body)
{/*{{{*/
    SVCNODE sn;
    char *request;
    char respond[30];
    int res;
    int rtn;

    if(!mailfwd_addr || !mailto || !mailfrom || !subject || !body ||
            strlen(body) > PARA_ITEM_MAX_SIZE)
        return(RET_ERR_PARA);

    if((res=sn_create(mailfwd_addr, SN_MODE_ONESHOT, &sn)))
        return(-1);
    ALLOC_MULTI_DOERR(request, char, strlen(mailto)+strlen(mailfrom)+
            FILE_NM_SIZE+strlen(subject)+strlen(body)+50,
            sn_free(sn); return(RET_ERR_ALLOC));
    CLR_BUF(request);
    para_put_item(request, mailto);
    para_put_item(request, mailfrom);
    if(replyto)
        para_put_item(request, replyto);
    else
        para_put_item(request, SPACES);
    para_put_item(request, subject);
    para_put_item(request, body);
    if((res=sn_call(sn, "SEND", request, respond)))
    {   rtn=-2;   }
    else
    {
        char *p=respond;
        rtn=atoi(para_get_item(respond, &p));
    }
    free(request);
    sn_free(sn);

    return(rtn);
}/*}}}*/

int send_mail(char *mailfwd_addr,
        char *mailto, char *mailfrom, char *replyto,
        char *subject, char *body)
{/*{{{*/
    return(_send_mail("SEND", mailfwd_addr, mailto, mailfrom,
                replyto, subject, body));
}/*}}}*/

int send_mail_vip(char *mailfwd_addr,
        char *mailto, char *mailfrom, char *replyto,
        char *subject, char *body)
{/*{{{*/
    return(_send_mail("SENDVIP", mailfwd_addr, mailto, mailfrom,
                replyto, subject, body));
}/*}}}*/
